1 Introduction

This weeks walk through will be similar to last week, with the addition of many enhancements for geographical data mapping. Throughout the lesson, the code will always be using leaflet for it’s plotting capabilities. We will first revisit the Philly crime data in a much more visually pleasing way. After that, typical data transformations will be demonstrated against two presidential election datasets. Lastly, we will create a choropleth map which can help us easily visualize which states are primarily republican, primarily democrat, and which states were swing states in the 2020 election. Let’s get started.

2 Philly Crimes

2.1 Data Transformation

The Philly crime data set does not support the capability to make a choropleth map. However, the dataset holds info at the neighborhood level (amongst other levels). The idea in this section is to come up with a map that imitates a choropleth map by leveraging grouping mechanisms at the neighborhood level. First, we start out by retrieving our neighborhood shape file along with the crime data itself.

philly_data <- read.csv("https://jmartin12.github.io/STAT553/data/PhillyCrimeSince2015.csv", header = TRUE)
neighborhood_shape_file <- st_read("https://pengdsci.github.io/STA553VIZ/w08/Neighborhoods_Philadelphia.geojson", quiet=TRUE)

Here are the two data sets visualized in table format, the first is the philly crime data, and the latter is the shape file.

kable(head(philly_data, 1), row.names = FALSE)
dc_key race sex fatal date has_court_case age street_name block_number zip_code council_district police_district neighborhood house_district senate_district school_catchment lng lat
2.02422E+11 Black (Non-Hispanic) Female Nonfatal 3/3/2024 14:49 No 20 N COLORADO ST 2500 19132 5 22 Sharswood-Stanton 181 3 Tanner G. Duckrey School -75.1606 39.99166
kable(head(neighborhood_shape_file, 1), row.names = FALSE)
name listname mapname shape_leng shape_area cartodb_id created_at updated_at geometry
PENNYPACK_PARK Pennypack Park Pennypack Park 87084.29 60140756 9 2013-03-19 13:41:50 2013-03-19 13:41:50 MULTIPOLYGON (((-75.05645 4…

We won’t be merging these two datasets as there aren’t any common values between them. The important column to note on the shapefile is the geometry column. These hold the sets of long/lat coordinates to make the multipolygons required for a given shape.


We can clean up our Philly data as we won’t use many of the column listed. Please see the filtered dataset below which includes only the columns that we will use in this excercise.

filtered_philly <- philly_data %>%
  select(race, sex, fatal, date, age, zip_code, neighborhood, school_catchment, lng, lat)

kable(head(filtered_philly, 2), row.names = FALSE)
race sex fatal date age zip_code neighborhood school_catchment lng lat
Black (Non-Hispanic) Female Nonfatal 3/3/2024 14:49 20 19132 Sharswood-Stanton Tanner G. Duckrey School -75.16060 39.99166
Hispanic (Black or White) Male Nonfatal 3/1/2024 22:18 58 19133 Northern Liberties-West Kensington John F. Hartranft School -75.14468 39.99152

Next, we need a dataset which includes a crime count for a given zipcode. These transformations allow the program to determine the total amount of fatal vs non-fatal crimes that have been committed in a given zip. The lines to note are the aggregate functions and the merge functions. In addition there are simple column renames and select statements to provide a clean dataset.

zip_lon = aggregate(filtered_philly$lng, by=list(filtered_philly$zip_code), FUN=mean)
zip_lat = aggregate(filtered_philly$lat, by=list(filtered_philly$zip_code), FUN=mean)
zip_location = merge(zip_lon, zip_lat, by = "Group.1")
names(zip_location) = c("zip", "lon", "lat")


crime_by_zip = data.frame(
                      zip=as.numeric(names(table(filtered_philly$zip_code))), 
                      fatal = table(filtered_philly$fatal, filtered_philly$zip_code)[1,],
                      nonfatal = table(filtered_philly$fatal, filtered_philly$zip_code)[2,],
                      total_crime = table(filtered_philly$zip_code) 
                      )

# Remove extra columns that we from the merges. 
dedupe_zip_column <- crime_by_zip %>%
  select(zip, fatal, nonfatal, total_crime.Freq)

# Rename the columns to something simple.
colnames(dedupe_zip_column) = c("zip", "fatal", "nonfatal", "total_crime_count")

# Finally, merge the grouped zipcode crime count dataset with the location itself.
zip_crime_with_location = merge(zip_location, dedupe_zip_column, by = "zip")
kable(head(zip_crime_with_location, 2), row.names = FALSE)
zip lon lat fatal nonfatal total_crime_count
19102 -75.16565 39.95178 5 17 22
19103 -75.17142 39.95206 1 9 10

While the following code is not displayed by default, this uses the two datasets of the transformed crime counts and neighborhood shape file. We then simply plot the crime locations based on their long/lat, and show some information about the crime counts themselves in the popup. This map will be used as a popup in the main map.

2.2 Philly Graph

Now that we have all of our data, and subplot, we can go ahead and create our bubble map. This bubble map has some custom features to make it look visually appealing to the user. Please see comments in the code for specifics of what is occurring.

# Main map.

# A simple title, styled to be darkred.
title <- tags$div(
  HTML('<font color = "darkred" size =4 style="background-color: transparent;"><b>Philly Fatal and Non-Fatal Crimes</b></font>')
)

# Color mapping, use wongs pallete
pal <- colorFactor(c("#000000", "#CC79A7"), domain = c("Fatal", "Nonfatal"))

main_map <- leaflet() %>%
  # The center of the map, via trial and error.
  setView(lng=-75.15092, lat=40.00995, zoom = 10) %>%
  # Viewing Options for the user.
  addProviderTiles(providers$CartoDB.DarkMatter, group="Dark") %>%
  addProviderTiles(providers$CartoDB.DarkMatterNoLabels, group="DarkLabel") %>%  
  addProviderTiles(providers$Esri.NatGeoWorldMap, group = "Esri") %>%
  # Title
  addControl(title, position = "bottomleft") %>%
  # Mini-map
  addMiniMap() %>%
  # Neighborhoods, using the shapefile. 
  addPolygons(data = neighborhood_shape_file,
            color = 'skyblue',
            weight = 1)  %>%
  # Note the `radius` and `color` logic. 
  addCircleMarkers(data = filtered_philly,
             radius = ~ifelse(fatal == "Fatal", 5, 3),
             color = ~pal(fatal),
             stroke = FALSE,
             fillOpacity = 0.5,
             # To allow the points not to clutter the screen, group them.
             clusterOptions = markerClusterOptions(maxClusterRadius = 40)) %>%
  # Use the previously created scatter map.
  addCircleMarkers(data = crime_by_zip_popup_location, 
                  color = "white",
                  weight = 2,
                  label = "ZIP Location",
                  stroke = FALSE, 
                  fillOpacity = 0.95,
                  group = "ziploc") %>%
  # Add it as an HTML iFrame popup.
  leafpop:::addPopupIframes(
                 source = fl,
                 width = 500,
                 height = 400,
                 group = "ziploc" ) %>%
  # Give many viewing options
  addLayersControl(baseGroups = c('Dark', 'DarkLabel', 'Esri'),
             options = layersControlOptions(collapsed = TRUE)) %>%
  ## Remove most of the junk at the bottom
  browsable()
    
main_map

3 2020 Election Resuts

3.1 Data Transformation

This section’s data transformation is actually much simpler than the previous data transformation section. We will reach out and obtain the required metadata to aggregate and show results, merge datasets so we have only one common dataframe to work with, and then provide filtering to only include the columns our graph will use. The code below demonstrates all of this; please see the individual comments for what each line performs.

# Get our data sets.
fips_meta <- read.csv("https://jmartin12.github.io/STAT553/data/fips2geocode.csv", header = TRUE)
election_meta <- st_read("https://jmartin12.github.io/STAT553/data/election_data.csv", quiet=TRUE)
stateShape <- st_read("https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json", quiet=TRUE) %>%
  rename(state = name) %>%
  mutate(state = toupper(state))

# Used for debugging.
#kable(head(fips_meta, 2), row.names = FALSE)
#kable(head(election_meta, 2), row.names = FALSE)

# Filter down to democrats and republicans, with the relevant voting data.
filtered_election <- election_meta %>%
  filter(year == 2020, party %in% c("REPUBLICAN", "DEMOCRAT")) %>%
  select(state, state_po, county_name, county_fips, party, candidatevotes)

# Used for debugging.
# kable(head(filtered_election, 2), row.names = FALSE)

# Merge the datasets to be able to include the lon / lat of votes for each state. 
election_with_location <- merge(filtered_election, fips_meta, by.x = "county_fips", by.y = "fips") %>%
 select(county_fips,    state_po,   county_name,    party,  candidatevotes, county, state.x,    lon,    lat)

# Used for debugging.
#kable(head(election_with_location, 2), row.names = FALSE)
#kable(head(stateShape, 2), row.names = FALSE)

# Merge so that we include our shape metadata.
election_with_shape <- merge(stateShape, election_with_location, by.x = "state", by.y = "state.x") %>%
  select(state, county_fips, state_po, county_name, party, candidatevotes, lon, lat, geometry)

# Transformation to a very specific datatype that leaflet wants to use when mapping choropleth maps. 
election_final <- st_as_sf(election_with_shape, coords = c("lon", "lat"), crs = 4326)

# Show our final dataset,
kable(head(election_final, 2), row.names = FALSE)
state county_fips state_po county_name party candidatevotes lon lat geometry
ALABAMA 1115 AL ST. CLAIR REPUBLICAN 36166 -86.31393 33.72447 MULTIPOLYGON (((-87.3593 35…
ALABAMA 1117 AL SHELBY DEMOCRAT 33268 -86.66510 33.26761 MULTIPOLYGON (((-87.3593 35…

Nice! We have a transformed dataset that can be used with the purpose of showing which states are primarly democratic or republican.

3.2 Election Choropleth Map

The purpose of the visualization below is to show which states are more biased towards being republican or democrat. The states that are a bright red are considered to be heavily biased towards being republican. The states that are a bright blue are considered to be heavily biased towards being democratic. The states which are a purple-ish color represent a divide of republicans and democrats. We leverage the total amount of candidate votes in a given state, grouped by their party to determine the color intensity.

get_color <- function(party) {
  if (party == "REPUBLICAN") {
    return("red")
  } else {
    return("blue")
  }
}

# Plot the election results with customized polygon features.
election_map <- leaflet() %>%
  setView(lng = -98.5833, lat = 39.8333, zoom = 4) %>%   # Centered on the US 
  addPolygons(data = election_final,
              fillColor = ifelse(election_final$party == "REPUBLICAN", "red", "blue"),
              fillOpacity = 0.5,
              color = "white",
              weight = 1,
              label = ~paste('State: ', state))

# Add a simple legend.
election_map <- addLegend(
  election_map,
  position = "topright",
  colors = c("blue", "red"),
  labels = c("Democrat", "Republican"),
  title = "Election Outcome"
)


election_map
LS0tCnRpdGxlOiAiV2VlayA4IC0gQWR2YW5jZWQgTWFwcyIKYXV0aG9yOiAiSmFjb2IgTWFydGluIgpkYXRlOiAiV2VzdCBDaGVzdGVyIFVuaXZlcnNpdHkgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA0CiAgICB0b2NfZmxvYXQ6IHllcwogICAgZmlnX3dpZHRoOiA2CiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdG9jX2NvbGxhcHNlZDogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHllcwogICAgc21vb3RoX3Njcm9sbDogdHJ1ZQogICAgdGhlbWU6IHJlYWRhYmxlCiAgICBmaWdfaGVpZ2h0OiA0Ci0tLQoKYGBgez1odG1sfQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoKZGl2I1RPQyBsaSB7CiAgICBsaXN0LXN0eWxlOm5vbmU7CiAgICBiYWNrZ3JvdW5kLWNvbG9yOmxpZ2h0Z3JheTsKICAgIGJhY2tncm91bmQtaW1hZ2U6bm9uZTsKICAgIGJhY2tncm91bmQtcmVwZWF0Om5vbmU7CiAgICBiYWNrZ3JvdW5kLXBvc2l0aW9uOjA7CiAgICBmb250LWZhbWlseTogQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZjsKICAgIGNvbG9yOiAjNzgwYzBjOwp9CgovKiBtb3VzZSBvdmVyIGxpbmsgKi8KZGl2I1RPQyBhOmhvdmVyIHsKICBjb2xvcjogcmVkOwp9CgovKiB1bnZpc2l0ZWQgbGluayAqLwpkaXYjVE9DIGE6bGluayB7CiAgY29sb3I6IGJsdWU7Cn0KCgoKaDEudGl0bGUgewogIGZvbnQtc2l6ZTogMjRweDsKICBjb2xvcjogRGFya2JsdWU7CiAgdGV4dC1hbGlnbjogY2VudGVyOwogIGZvbnQtZmFtaWx5OiBBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmOwogIGZvbnQtdmFyaWFudC1jYXBzOiBub3JtYWw7Cn0KaDQuYXV0aG9yIHsgCiAgICBmb250LXNpemU6IDE4cHg7CiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7CiAgY29sb3I6IERhcmtSZWQ7CiAgdGV4dC1hbGlnbjogY2VudGVyOwp9Cmg0LmRhdGUgeyAKICBmb250LXNpemU6IDE4cHg7CiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7CiAgY29sb3I6IERhcmtCbHVlOwogIHRleHQtYWxpZ246IGNlbnRlcjsKfQpoMSB7CiAgICBmb250LXNpemU6IDI0cHg7CiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsKICAgIGNvbG9yOiBkYXJrcmVkOwogICAgdGV4dC1hbGlnbjogY2VudGVyOwp9CmgyIHsKICAgIGZvbnQtc2l6ZTogMThweDsKICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOwogICAgY29sb3I6IG5hdnk7CiAgICB0ZXh0LWFsaWduOiBsZWZ0Owp9CgpoMyB7IAogICAgZm9udC1zaXplOiAxNXB4OwogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7CiAgICBjb2xvcjogbmF2eTsKICAgIHRleHQtYWxpZ246IGxlZnQ7Cn0KCmg0IHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8KICAgIGZvbnQtc2l6ZTogMThweDsKICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOwogICAgY29sb3I6IGRhcmtyZWQ7CiAgICB0ZXh0LWFsaWduOiBsZWZ0Owp9CgovKiB1bnZpc2l0ZWQgbGluayAqLwphOmxpbmsgewogIGNvbG9yOiBncmVlbjsKfQoKLyogdmlzaXRlZCBsaW5rICovCmE6dmlzaXRlZCB7CiAgY29sb3I6IGdyZWVuOwp9CgovKiBtb3VzZSBvdmVyIGxpbmsgKi8KYTpob3ZlciB7CiAgY29sb3I6IHJlZDsKfQoKLyogc2VsZWN0ZWQgbGluayAqLwphOmFjdGl2ZSB7CiAgY29sb3I6IHllbGxvdzsKfQoKPC9zdHlsZT4KYGBgCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIGNvZGUgY2h1bmsgc3BlY2lmaWVzIHdoZXRoZXIgdGhlIFIgY29kZSwgd2FybmluZ3MsIGFuZCBvdXRwdXQgCiMgd2lsbCBiZSBpbmNsdWRlZCBpbiB0aGUgb3V0cHV0IGZpbGVzLgpvcHRpb25zKHJlcG9zID0gbGlzdChDUkFOPSJodHRwOi8vY3Jhbi5yc3R1ZGlvLmNvbS8iKSkKaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQogICBsaWJyYXJ5KHRpZHl2ZXJzZSkKfQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQogICBsaWJyYXJ5KGtuaXRyKQp9CmlmICghcmVxdWlyZSgiY293cGxvdCIpKSB7CiAgIGluc3RhbGwucGFja2FnZXMoImNvd3Bsb3QiKQogICBsaWJyYXJ5KGNvd3Bsb3QpCn0KaWYgKCFyZXF1aXJlKCJsYXRleDJleHAiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJsYXRleDJleHAiKQogICBsaWJyYXJ5KGxhdGV4MmV4cCkKfQppZiAoIXJlcXVpcmUoInBsb3RseSIpKSB7CiAgIGluc3RhbGwucGFja2FnZXMoInBsb3RseSIpCiAgIGxpYnJhcnkocGxvdGx5KQp9CmlmICghcmVxdWlyZSgiZ2FwbWluZGVyIikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygiZ2FwbWluZGVyIikKICAgbGlicmFyeShnYXBtaW5kZXIpCn0KaWYgKCFyZXF1aXJlKCJwbmciKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcygicG5nIikgICAgICAgICAgICAgIyBJbnN0YWxsIHBuZyBwYWNrYWdlCiAgICBsaWJyYXJ5KCJwbmciKQp9CmlmICghcmVxdWlyZSgiUkN1cmwiKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcygiUkN1cmwiKSAgICAgICAgICAgIyBJbnN0YWxsIFJDdXJsIHBhY2thZ2UKICAgIGxpYnJhcnkoIlJDdXJsIikKfQppZiAoIXJlcXVpcmUoImNvbG91cnBpY2tlciIpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJjb2xvdXJwaWNrZXIiKSAgICAgICAgICAgICAgCiAgICBsaWJyYXJ5KCJjb2xvdXJwaWNrZXIiKQp9CmlmICghcmVxdWlyZSgiZ2lmc2tpIikpIHsKICAgIGluc3RhbGwucGFja2FnZXMoImdpZnNraSIpICAgICAgICAgICAgICAKICAgIGxpYnJhcnkoImdpZnNraSIpCn0KaWYgKCFyZXF1aXJlKCJtYWdpY2siKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcygibWFnaWNrIikgICAgICAgICAgICAgIAogICAgbGlicmFyeSgibWFnaWNrIikKfQppZiAoIXJlcXVpcmUoImdyRGV2aWNlcyIpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJnckRldmljZXMiKSAgICAgICAgICAgICAgCiAgICBsaWJyYXJ5KCJnckRldmljZXMiKQp9CiMjIyBnZ3Bsb3QgYW5kIGV4dGVuc2lvbnMKaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpIHsKICAgIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKSAgICAgICAgICAgICAgCiAgICBsaWJyYXJ5KCJnZ3Bsb3QyIikKfQppZiAoIXJlcXVpcmUoImdnYW5pbWF0ZSIpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJnZ2FuaW1hdGUiKSAgICAgICAgICAgICAgCiAgICBsaWJyYXJ5KCJnZ2FuaW1hdGUiKQp9CmlmICghcmVxdWlyZSgiZ2dyaWRnZXMiKSkgewogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dyaWRnZXMiKSAgICAgICAgICAgICAgCiAgICBsaWJyYXJ5KCJnZ3JpZGdlcyIpCn0KaWYgKCFyZXF1aXJlKCJncmFwaGljcyIpKSB7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJncmFwaGljcyIpICAgICAgICAgICAgICAKICAgIGxpYnJhcnkoImdyYXBoaWNzIikKfQppZiAoIXJlcXVpcmUoInRpZHlyIikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygidGlkeXIiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQogICBsaWJyYXJ5KHRpZHlyKQp9CmlmICghcmVxdWlyZSgicmVzaGFwZTIiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJyZXNoYXBlMiIsIGRlcGVuZGVuY2llcyA9IFRSVUUpCiAgIGxpYnJhcnkocmVzaGFwZTIpCn0KaWYgKCFyZXF1aXJlKCJsZWFmbGV0IikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygibGVhZmxldCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpCiAgIGxpYnJhcnkobGVhZmxldCkKfQppZiAoIXJlcXVpcmUoImxlYWZsZXQuZXh0cmFzIikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygibGVhZmxldC5leHRyYXMiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQogICBsaWJyYXJ5KGxlYWZsZXQuZXh0cmFzKQp9CmlmICghcmVxdWlyZSgibGVhZmxldC5leHRyYXMiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJsZWFmcG9wIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkKICAgbGlicmFyeShsZWFmcG9wKQp9CmlmICghcmVxdWlyZSgiaHRtbHRvb2xzIikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygiaHRtbHRvb2xzIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkKICAgbGlicmFyeShodG1sdG9vbHMpCn0KaWYgKCFyZXF1aXJlKCJodG1sd2lkZ2V0cyIpKSB7CiAgIGluc3RhbGwucGFja2FnZXMoImh0bWx3aWRnZXRzIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkKICAgbGlicmFyeShodG1sd2lkZ2V0cykKfQppZiAoIXJlcXVpcmUoInNmIikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygic2YiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQogICBsaWJyYXJ5KHNmKQp9CmlmICghcmVxdWlyZSgiZHBseXIiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIsIGRlcGVuZGVuY2llcyA9IFRSVUUpCiAgIGxpYnJhcnkoZHBseXIpCn0KaWYgKCFyZXF1aXJlKCJ2aXJpZGlzIikpIHsKICAgaW5zdGFsbC5wYWNrYWdlcygidmlyaWRpcyIsIGRlcGVuZGVuY2llcyA9IFRSVUUpCiAgIGxpYnJhcnkodmlyaWRpcykKfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgIAogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gVFJVRSwgICAKICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQSkKYGBgCgojIEludHJvZHVjdGlvbgpUaGlzIHdlZWtzIHdhbGsgdGhyb3VnaCB3aWxsIGJlIHNpbWlsYXIgdG8gbGFzdCB3ZWVrLCB3aXRoIHRoZSBhZGRpdGlvbiBvZiBtYW55IGVuaGFuY2VtZW50cyBmb3IgZ2VvZ3JhcGhpY2FsIGRhdGEgbWFwcGluZy4gVGhyb3VnaG91dCB0aGUgbGVzc29uLCB0aGUgY29kZSB3aWxsIGFsd2F5cyBiZSB1c2luZyBgbGVhZmxldGAgZm9yIGl0J3MgcGxvdHRpbmcgY2FwYWJpbGl0aWVzLiBXZSB3aWxsIGZpcnN0IHJldmlzaXQgdGhlIFBoaWxseSBjcmltZSBkYXRhIGluIGEgbXVjaCBtb3JlIHZpc3VhbGx5IHBsZWFzaW5nIHdheS4gQWZ0ZXIgdGhhdCwgdHlwaWNhbCBkYXRhIHRyYW5zZm9ybWF0aW9ucyB3aWxsIGJlIGRlbW9uc3RyYXRlZCBhZ2FpbnN0IHR3byBwcmVzaWRlbnRpYWwgZWxlY3Rpb24gZGF0YXNldHMuIExhc3RseSwgd2Ugd2lsbCBjcmVhdGUgYSBjaG9yb3BsZXRoIG1hcCB3aGljaCBjYW4gaGVscCB1cyBlYXNpbHkgdmlzdWFsaXplIHdoaWNoIHN0YXRlcyBhcmUgcHJpbWFyaWx5IHJlcHVibGljYW4sIHByaW1hcmlseSBkZW1vY3JhdCwgYW5kIHdoaWNoIHN0YXRlcyB3ZXJlIHN3aW5nIHN0YXRlcyBpbiB0aGUgMjAyMCBlbGVjdGlvbi4gTGV0J3MgZ2V0IHN0YXJ0ZWQuCgojIFBoaWxseSBDcmltZXMKCiMjIERhdGEgVHJhbnNmb3JtYXRpb24KClRoZSBQaGlsbHkgY3JpbWUgZGF0YSBzZXQgZG9lcyBub3Qgc3VwcG9ydCB0aGUgY2FwYWJpbGl0eSB0byBtYWtlIGEgY2hvcm9wbGV0aCBtYXAuIEhvd2V2ZXIsIHRoZSBkYXRhc2V0IGhvbGRzIGluZm8gYXQgdGhlIG5laWdoYm9yaG9vZCBsZXZlbCAoYW1vbmdzdCBvdGhlciBsZXZlbHMpLiBUaGUgaWRlYSBpbiB0aGlzIHNlY3Rpb24gaXMgdG8gY29tZSB1cCB3aXRoIGEgbWFwIHRoYXQgaW1pdGF0ZXMgYSBjaG9yb3BsZXRoIG1hcCBieSBsZXZlcmFnaW5nIGdyb3VwaW5nIG1lY2hhbmlzbXMgYXQgdGhlIG5laWdoYm9yaG9vZCBsZXZlbC4gRmlyc3QsIHdlIHN0YXJ0IG91dCBieSByZXRyaWV2aW5nIG91ciBuZWlnaGJvcmhvb2Qgc2hhcGUgZmlsZSBhbG9uZyB3aXRoIHRoZSBjcmltZSBkYXRhIGl0c2VsZi4KCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGhpbGx5X2RhdGEgPC0gcmVhZC5jc3YoImh0dHBzOi8vam1hcnRpbjEyLmdpdGh1Yi5pby9TVEFUNTUzL2RhdGEvUGhpbGx5Q3JpbWVTaW5jZTIwMTUuY3N2IiwgaGVhZGVyID0gVFJVRSkKbmVpZ2hib3Job29kX3NoYXBlX2ZpbGUgPC0gc3RfcmVhZCgiaHR0cHM6Ly9wZW5nZHNjaS5naXRodWIuaW8vU1RBNTUzVklaL3cwOC9OZWlnaGJvcmhvb2RzX1BoaWxhZGVscGhpYS5nZW9qc29uIiwgcXVpZXQ9VFJVRSkKYGBgCgpIZXJlIGFyZSB0aGUgdHdvIGRhdGEgc2V0cyB2aXN1YWxpemVkIGluIHRhYmxlIGZvcm1hdCwgdGhlIGZpcnN0IGlzIHRoZSBwaGlsbHkgY3JpbWUgZGF0YSwgYW5kIHRoZSBsYXR0ZXIgaXMgdGhlIHNoYXBlIGZpbGUuIAoKYGBge3IgZWNobz1UUlVFfQprYWJsZShoZWFkKHBoaWxseV9kYXRhLCAxKSwgcm93Lm5hbWVzID0gRkFMU0UpCmthYmxlKGhlYWQobmVpZ2hib3Job29kX3NoYXBlX2ZpbGUsIDEpLCByb3cubmFtZXMgPSBGQUxTRSkKYGBgCgpXZSB3b24ndCBiZSBtZXJnaW5nIHRoZXNlIHR3byBkYXRhc2V0cyBhcyB0aGVyZSBhcmVuJ3QgYW55IGNvbW1vbiB2YWx1ZXMgYmV0d2VlbiB0aGVtLiBUaGUgaW1wb3J0YW50IGNvbHVtbiB0byBub3RlIG9uIHRoZSBzaGFwZWZpbGUgaXMgdGhlIGBnZW9tZXRyeWAgY29sdW1uLiBUaGVzZSBob2xkIHRoZSBzZXRzIG9mIGxvbmcvbGF0IGNvb3JkaW5hdGVzIHRvIG1ha2UgdGhlIG11bHRpcG9seWdvbnMgcmVxdWlyZWQgZm9yIGEgZ2l2ZW4gc2hhcGUuIAoKXAoKV2UgY2FuIGNsZWFuIHVwIG91ciBQaGlsbHkgZGF0YSBhcyB3ZSB3b24ndCB1c2UgbWFueSBvZiB0aGUgY29sdW1uIGxpc3RlZC4gUGxlYXNlIHNlZSB0aGUgZmlsdGVyZWQgZGF0YXNldCBiZWxvdyB3aGljaCBpbmNsdWRlcyBvbmx5IHRoZSBjb2x1bW5zIHRoYXQgd2Ugd2lsbCB1c2UgaW4gdGhpcyBleGNlcmNpc2UuCgpgYGB7cn0KCmZpbHRlcmVkX3BoaWxseSA8LSBwaGlsbHlfZGF0YSAlPiUKICBzZWxlY3QocmFjZSwgc2V4LCBmYXRhbCwgZGF0ZSwgYWdlLCB6aXBfY29kZSwgbmVpZ2hib3Job29kLCBzY2hvb2xfY2F0Y2htZW50LCBsbmcsIGxhdCkKCmthYmxlKGhlYWQoZmlsdGVyZWRfcGhpbGx5LCAyKSwgcm93Lm5hbWVzID0gRkFMU0UpCmBgYAoKCk5leHQsIHdlIG5lZWQgYSBkYXRhc2V0IHdoaWNoIGluY2x1ZGVzIGEgY3JpbWUgY291bnQgZm9yIGEgZ2l2ZW4gemlwY29kZS4KVGhlc2UgdHJhbnNmb3JtYXRpb25zIGFsbG93IHRoZSBwcm9ncmFtIHRvIGRldGVybWluZSB0aGUgdG90YWwgYW1vdW50IG9mIGZhdGFsIHZzIG5vbi1mYXRhbCBjcmltZXMgdGhhdCBoYXZlIGJlZW4gY29tbWl0dGVkIGluIGEgZ2l2ZW4gemlwLgpUaGUgbGluZXMgdG8gbm90ZSBhcmUgdGhlIGBhZ2dyZWdhdGVgIGZ1bmN0aW9ucyBhbmQgdGhlIGBtZXJnZWAgZnVuY3Rpb25zLgpJbiBhZGRpdGlvbiB0aGVyZSBhcmUgc2ltcGxlIGNvbHVtbiByZW5hbWVzIGFuZCBgc2VsZWN0YCBzdGF0ZW1lbnRzIHRvIHByb3ZpZGUgYSBjbGVhbiBkYXRhc2V0LgoKYGBge3J9CnppcF9sb24gPSBhZ2dyZWdhdGUoZmlsdGVyZWRfcGhpbGx5JGxuZywgYnk9bGlzdChmaWx0ZXJlZF9waGlsbHkkemlwX2NvZGUpLCBGVU49bWVhbikKemlwX2xhdCA9IGFnZ3JlZ2F0ZShmaWx0ZXJlZF9waGlsbHkkbGF0LCBieT1saXN0KGZpbHRlcmVkX3BoaWxseSR6aXBfY29kZSksIEZVTj1tZWFuKQp6aXBfbG9jYXRpb24gPSBtZXJnZSh6aXBfbG9uLCB6aXBfbGF0LCBieSA9ICJHcm91cC4xIikKbmFtZXMoemlwX2xvY2F0aW9uKSA9IGMoInppcCIsICJsb24iLCAibGF0IikKCgpjcmltZV9ieV96aXAgPSBkYXRhLmZyYW1lKAogICAgICAgICAgICAgICAgICAgICAgemlwPWFzLm51bWVyaWMobmFtZXModGFibGUoZmlsdGVyZWRfcGhpbGx5JHppcF9jb2RlKSkpLCAKICAgICAgICAgICAgICAgICAgICAgIGZhdGFsID0gdGFibGUoZmlsdGVyZWRfcGhpbGx5JGZhdGFsLCBmaWx0ZXJlZF9waGlsbHkkemlwX2NvZGUpWzEsXSwKICAgICAgICAgICAgICAgICAgICAgIG5vbmZhdGFsID0gdGFibGUoZmlsdGVyZWRfcGhpbGx5JGZhdGFsLCBmaWx0ZXJlZF9waGlsbHkkemlwX2NvZGUpWzIsXSwKICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2NyaW1lID0gdGFibGUoZmlsdGVyZWRfcGhpbGx5JHppcF9jb2RlKSAKICAgICAgICAgICAgICAgICAgICAgICkKCiMgUmVtb3ZlIGV4dHJhIGNvbHVtbnMgdGhhdCB3ZSBmcm9tIHRoZSBtZXJnZXMuIApkZWR1cGVfemlwX2NvbHVtbiA8LSBjcmltZV9ieV96aXAgJT4lCiAgc2VsZWN0KHppcCwgZmF0YWwsIG5vbmZhdGFsLCB0b3RhbF9jcmltZS5GcmVxKQoKIyBSZW5hbWUgdGhlIGNvbHVtbnMgdG8gc29tZXRoaW5nIHNpbXBsZS4KY29sbmFtZXMoZGVkdXBlX3ppcF9jb2x1bW4pID0gYygiemlwIiwgImZhdGFsIiwgIm5vbmZhdGFsIiwgInRvdGFsX2NyaW1lX2NvdW50IikKCiMgRmluYWxseSwgbWVyZ2UgdGhlIGdyb3VwZWQgemlwY29kZSBjcmltZSBjb3VudCBkYXRhc2V0IHdpdGggdGhlIGxvY2F0aW9uIGl0c2VsZi4KemlwX2NyaW1lX3dpdGhfbG9jYXRpb24gPSBtZXJnZSh6aXBfbG9jYXRpb24sIGRlZHVwZV96aXBfY29sdW1uLCBieSA9ICJ6aXAiKQprYWJsZShoZWFkKHppcF9jcmltZV93aXRoX2xvY2F0aW9uLCAyKSwgcm93Lm5hbWVzID0gRkFMU0UpCgpgYGAKCldoaWxlIHRoZSBmb2xsb3dpbmcgY29kZSBpcyBub3QgZGlzcGxheWVkIGJ5IGRlZmF1bHQsIHRoaXMgdXNlcyB0aGUgdHdvIGRhdGFzZXRzIG9mIHRoZSB0cmFuc2Zvcm1lZCBjcmltZSBjb3VudHMgYW5kIG5laWdoYm9yaG9vZCBzaGFwZSBmaWxlLiBXZSB0aGVuIHNpbXBseSBwbG90IHRoZSBjcmltZSBsb2NhdGlvbnMgYmFzZWQgb24gdGhlaXIgbG9uZy9sYXQsIGFuZCBzaG93IHNvbWUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGNyaW1lIGNvdW50cyB0aGVtc2VsdmVzIGluIHRoZSBwb3B1cC4gClRoaXMgbWFwIHdpbGwgYmUgdXNlZCBhcyBhIHBvcHVwIGluIHRoZSBtYWluIG1hcC4KYGBge3IgZWNobz1GQUxTRX0KCgojIENvbG9ycyBmb3IgdGhlIGJ1YmJsZSBwbG90cwpjb2xvcl9yYW5nZSA8LSBjb2xvck51bWVyaWMocGFsZXR0ZSA9IHZpcmlkaXMoMjU2LCBvcHRpb24gPSAiQiIpLCBkb21haW4gPSByYW5nZSh6aXBfY3JpbWVfd2l0aF9sb2NhdGlvbiR0b3RhbF9jcmltZV9jb3VudCkpCgpjcmltZV9ieV96aXBfbWFwIDwtIGxlYWZsZXQoZmlsdGVyZWRfcGhpbGx5KSAlPiUKICBzZXRWaWV3KGxuZz0tNzUuMTUyNywgbGF0PTM5Ljk3MDcsIHpvb20gPSAxMSkgJT4lCiAgYWRkVGlsZXMoKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRFc3JpLldvcmxkR3JheUNhbnZhcykgJT4lCiAgIyBUaGUgbmVpZ2hib3Job29kcy4KICBhZGRQb2x5Z29ucyhkYXRhID0gbmVpZ2hib3Job29kX3NoYXBlX2ZpbGUsCiAgICAgICAgICAgIGNvbG9yID0gJ3NreWJsdWUnLAogICAgICAgICAgICB3ZWlnaHQgPSAxKSAgJT4lCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhID0gemlwX2NyaW1lX3dpdGhfbG9jYXRpb24sCiAgICAgICAgICAgICAgICAgICByYWRpdXMgPSB+KCh0b3RhbF9jcmltZV9jb3VudCleKDEvMykpLAogICAgICAgICAgICAgICAgICAgY29sb3IgPSB+Y29sb3JfcmFuZ2UodG90YWxfY3JpbWVfY291bnQpLAogICAgICAgICAgICAgICAgICAgc3Ryb2tlID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjUsCiAgICAgICAgICAgICAgICAgICBwb3B1cCA9IH5wYXN0ZSgnWmlwIENvZGU6JywgemlwLCAKICAgICAgICAgICAgICAgICAgICAgICc8YnI+VG90YWwgQ3JpbWU6JywgdG90YWxfY3JpbWVfY291bnQsIAogICAgICAgICAgICAgICAgICAgICAgJzxicj5GYXRhbCBDcmltZTonLCBmYXRhbCwKICAgICAgICAgICAgICAgICAgICAgICc8YnI+Tm9uZmF0YWwgQ3JpbWU6Jywgbm9uZmF0YWwpKSAKCiMgU2F2ZSB0byB0ZW1wIGZpbGUgZm9yIGZ1dHVyZSByZWZlcmVuY2UgaW4gbWFpbiBtYXAuCmZsID0gdGVtcGZpbGUoZmlsZWV4dCA9ICIuaHRtbCIpCnNhdmVXaWRnZXQoY3JpbWVfYnlfemlwX21hcCwgZmlsZSA9IGZsKQoKIyBXaGVyZSB0byBwdXQgdGhlIG1hcCwgdHJpYWwgYW5kIGVycm9yLgpjcmltZV9ieV96aXBfcG9wdXBfbG9jYXRpb24gPSBzdF9hc19zZihkYXRhLmZyYW1lKHggPSAtNzUuMzQ3NywgeSA9IDM5LjkxNjgpLAogICAgICAgICAgICAgICAgY29vcmRzID0gYygieCIsICJ5IiksCiAgICAgICAgICAgICAgICBjcnMgPSA0MzI2KQpgYGAKCgojIyBQaGlsbHkgR3JhcGgKCk5vdyB0aGF0IHdlIGhhdmUgYWxsIG9mIG91ciBkYXRhLCBhbmQgc3VicGxvdCwgd2UgY2FuIGdvIGFoZWFkIGFuZCBjcmVhdGUgb3VyIGJ1YmJsZSBtYXAuIApUaGlzIGJ1YmJsZSBtYXAgaGFzIHNvbWUgY3VzdG9tIGZlYXR1cmVzIHRvIG1ha2UgaXQgbG9vayB2aXN1YWxseSBhcHBlYWxpbmcgdG8gdGhlIHVzZXIuIApQbGVhc2Ugc2VlIGNvbW1lbnRzIGluIHRoZSBjb2RlIGZvciBzcGVjaWZpY3Mgb2Ygd2hhdCBpcyBvY2N1cnJpbmcuIAoKYGBge3IgZWNobz1UUlVFfQoKIyBNYWluIG1hcC4KCiMgQSBzaW1wbGUgdGl0bGUsIHN0eWxlZCB0byBiZSBkYXJrcmVkLgp0aXRsZSA8LSB0YWdzJGRpdigKICBIVE1MKCc8Zm9udCBjb2xvciA9ICJkYXJrcmVkIiBzaXplID00IHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsiPjxiPlBoaWxseSBGYXRhbCBhbmQgTm9uLUZhdGFsIENyaW1lczwvYj48L2ZvbnQ+JykKKQoKIyBDb2xvciBtYXBwaW5nLCB1c2Ugd29uZ3MgcGFsbGV0ZQpwYWwgPC0gY29sb3JGYWN0b3IoYygiIzAwMDAwMCIsICIjQ0M3OUE3IiksIGRvbWFpbiA9IGMoIkZhdGFsIiwgIk5vbmZhdGFsIikpCgptYWluX21hcCA8LSBsZWFmbGV0KCkgJT4lCiAgIyBUaGUgY2VudGVyIG9mIHRoZSBtYXAsIHZpYSB0cmlhbCBhbmQgZXJyb3IuCiAgc2V0Vmlldyhsbmc9LTc1LjE1MDkyLCBsYXQ9NDAuMDA5OTUsIHpvb20gPSAxMCkgJT4lCiAgIyBWaWV3aW5nIE9wdGlvbnMgZm9yIHRoZSB1c2VyLgogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuRGFya01hdHRlciwgZ3JvdXA9IkRhcmsiKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLkRhcmtNYXR0ZXJOb0xhYmVscywgZ3JvdXA9IkRhcmtMYWJlbCIpICU+JSAgCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkRXNyaS5OYXRHZW9Xb3JsZE1hcCwgZ3JvdXAgPSAiRXNyaSIpICU+JQogICMgVGl0bGUKICBhZGRDb250cm9sKHRpdGxlLCBwb3NpdGlvbiA9ICJib3R0b21sZWZ0IikgJT4lCiAgIyBNaW5pLW1hcAogIGFkZE1pbmlNYXAoKSAlPiUKICAjIE5laWdoYm9yaG9vZHMsIHVzaW5nIHRoZSBzaGFwZWZpbGUuIAogIGFkZFBvbHlnb25zKGRhdGEgPSBuZWlnaGJvcmhvb2Rfc2hhcGVfZmlsZSwKICAgICAgICAgICAgY29sb3IgPSAnc2t5Ymx1ZScsCiAgICAgICAgICAgIHdlaWdodCA9IDEpICAlPiUKICAjIE5vdGUgdGhlIGByYWRpdXNgIGFuZCBgY29sb3JgIGxvZ2ljLiAKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBmaWx0ZXJlZF9waGlsbHksCiAgICAgICAgICAgICByYWRpdXMgPSB+aWZlbHNlKGZhdGFsID09ICJGYXRhbCIsIDUsIDMpLAogICAgICAgICAgICAgY29sb3IgPSB+cGFsKGZhdGFsKSwKICAgICAgICAgICAgIHN0cm9rZSA9IEZBTFNFLAogICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjUsCiAgICAgICAgICAgICAjIFRvIGFsbG93IHRoZSBwb2ludHMgbm90IHRvIGNsdXR0ZXIgdGhlIHNjcmVlbiwgZ3JvdXAgdGhlbS4KICAgICAgICAgICAgIGNsdXN0ZXJPcHRpb25zID0gbWFya2VyQ2x1c3Rlck9wdGlvbnMobWF4Q2x1c3RlclJhZGl1cyA9IDQwKSkgJT4lCiAgIyBVc2UgdGhlIHByZXZpb3VzbHkgY3JlYXRlZCBzY2F0dGVyIG1hcC4KICBhZGRDaXJjbGVNYXJrZXJzKGRhdGEgPSBjcmltZV9ieV96aXBfcG9wdXBfbG9jYXRpb24sIAogICAgICAgICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsCiAgICAgICAgICAgICAgICAgIHdlaWdodCA9IDIsCiAgICAgICAgICAgICAgICAgIGxhYmVsID0gIlpJUCBMb2NhdGlvbiIsCiAgICAgICAgICAgICAgICAgIHN0cm9rZSA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgZmlsbE9wYWNpdHkgPSAwLjk1LAogICAgICAgICAgICAgICAgICBncm91cCA9ICJ6aXBsb2MiKSAlPiUKICAjIEFkZCBpdCBhcyBhbiBIVE1MIGlGcmFtZSBwb3B1cC4KICBsZWFmcG9wOjo6YWRkUG9wdXBJZnJhbWVzKAogICAgICAgICAgICAgICAgIHNvdXJjZSA9IGZsLAogICAgICAgICAgICAgICAgIHdpZHRoID0gNTAwLAogICAgICAgICAgICAgICAgIGhlaWdodCA9IDQwMCwKICAgICAgICAgICAgICAgICBncm91cCA9ICJ6aXBsb2MiICkgJT4lCiAgIyBHaXZlIG1hbnkgdmlld2luZyBvcHRpb25zCiAgYWRkTGF5ZXJzQ29udHJvbChiYXNlR3JvdXBzID0gYygnRGFyaycsICdEYXJrTGFiZWwnLCAnRXNyaScpLAogICAgICAgICAgICAgb3B0aW9ucyA9IGxheWVyc0NvbnRyb2xPcHRpb25zKGNvbGxhcHNlZCA9IFRSVUUpKSAlPiUKICAjIyBSZW1vdmUgbW9zdCBvZiB0aGUganVuayBhdCB0aGUgYm90dG9tCiAgYnJvd3NhYmxlKCkKICAgIAptYWluX21hcApgYGAKCiMgMjAyMCBFbGVjdGlvbiBSZXN1dHMKCiMjIERhdGEgVHJhbnNmb3JtYXRpb24KClRoaXMgc2VjdGlvbidzIGRhdGEgdHJhbnNmb3JtYXRpb24gaXMgYWN0dWFsbHkgbXVjaCBzaW1wbGVyIHRoYW4gdGhlIHByZXZpb3VzIGRhdGEgdHJhbnNmb3JtYXRpb24gc2VjdGlvbi4gV2Ugd2lsbCByZWFjaCBvdXQgYW5kIG9idGFpbiB0aGUgcmVxdWlyZWQgbWV0YWRhdGEgdG8gYWdncmVnYXRlIGFuZCBzaG93IHJlc3VsdHMsIG1lcmdlIGRhdGFzZXRzIHNvIHdlIGhhdmUgb25seSBvbmUgY29tbW9uIGRhdGFmcmFtZSB0byB3b3JrIHdpdGgsIGFuZCB0aGVuIHByb3ZpZGUgZmlsdGVyaW5nIHRvIG9ubHkgaW5jbHVkZSB0aGUgY29sdW1ucyBvdXIgZ3JhcGggd2lsbCB1c2UuIFRoZSBjb2RlIGJlbG93IGRlbW9uc3RyYXRlcyBhbGwgb2YgdGhpczsgcGxlYXNlIHNlZSB0aGUgaW5kaXZpZHVhbCBjb21tZW50cyBmb3Igd2hhdCBlYWNoIGxpbmUgcGVyZm9ybXMuCgpgYGB7ciBlY2hvPVRSVUV9CiMgR2V0IG91ciBkYXRhIHNldHMuCmZpcHNfbWV0YSA8LSByZWFkLmNzdigiaHR0cHM6Ly9qbWFydGluMTIuZ2l0aHViLmlvL1NUQVQ1NTMvZGF0YS9maXBzMmdlb2NvZGUuY3N2IiwgaGVhZGVyID0gVFJVRSkKZWxlY3Rpb25fbWV0YSA8LSBzdF9yZWFkKCJodHRwczovL2ptYXJ0aW4xMi5naXRodWIuaW8vU1RBVDU1My9kYXRhL2VsZWN0aW9uX2RhdGEuY3N2IiwgcXVpZXQ9VFJVRSkKc3RhdGVTaGFwZSA8LSBzdF9yZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vUHVibGljYU11bmRpL01hcHBpbmdBUEkvbWFzdGVyL2RhdGEvZ2VvanNvbi91cy1zdGF0ZXMuanNvbiIsIHF1aWV0PVRSVUUpICU+JQogIHJlbmFtZShzdGF0ZSA9IG5hbWUpICU+JQogIG11dGF0ZShzdGF0ZSA9IHRvdXBwZXIoc3RhdGUpKQoKIyBVc2VkIGZvciBkZWJ1Z2dpbmcuCiNrYWJsZShoZWFkKGZpcHNfbWV0YSwgMiksIHJvdy5uYW1lcyA9IEZBTFNFKQoja2FibGUoaGVhZChlbGVjdGlvbl9tZXRhLCAyKSwgcm93Lm5hbWVzID0gRkFMU0UpCgojIEZpbHRlciBkb3duIHRvIGRlbW9jcmF0cyBhbmQgcmVwdWJsaWNhbnMsIHdpdGggdGhlIHJlbGV2YW50IHZvdGluZyBkYXRhLgpmaWx0ZXJlZF9lbGVjdGlvbiA8LSBlbGVjdGlvbl9tZXRhICU+JQogIGZpbHRlcih5ZWFyID09IDIwMjAsIHBhcnR5ICVpbiUgYygiUkVQVUJMSUNBTiIsICJERU1PQ1JBVCIpKSAlPiUKICBzZWxlY3Qoc3RhdGUsIHN0YXRlX3BvLCBjb3VudHlfbmFtZSwgY291bnR5X2ZpcHMsIHBhcnR5LCBjYW5kaWRhdGV2b3RlcykKCiMgVXNlZCBmb3IgZGVidWdnaW5nLgojIGthYmxlKGhlYWQoZmlsdGVyZWRfZWxlY3Rpb24sIDIpLCByb3cubmFtZXMgPSBGQUxTRSkKCiMgTWVyZ2UgdGhlIGRhdGFzZXRzIHRvIGJlIGFibGUgdG8gaW5jbHVkZSB0aGUgbG9uIC8gbGF0IG9mIHZvdGVzIGZvciBlYWNoIHN0YXRlLiAKZWxlY3Rpb25fd2l0aF9sb2NhdGlvbiA8LSBtZXJnZShmaWx0ZXJlZF9lbGVjdGlvbiwgZmlwc19tZXRhLCBieS54ID0gImNvdW50eV9maXBzIiwgYnkueSA9ICJmaXBzIikgJT4lCiBzZWxlY3QoY291bnR5X2ZpcHMsCXN0YXRlX3BvLAljb3VudHlfbmFtZSwJcGFydHksCWNhbmRpZGF0ZXZvdGVzLAljb3VudHksCXN0YXRlLngsCWxvbiwJbGF0KQoKIyBVc2VkIGZvciBkZWJ1Z2dpbmcuCiNrYWJsZShoZWFkKGVsZWN0aW9uX3dpdGhfbG9jYXRpb24sIDIpLCByb3cubmFtZXMgPSBGQUxTRSkKI2thYmxlKGhlYWQoc3RhdGVTaGFwZSwgMiksIHJvdy5uYW1lcyA9IEZBTFNFKQoKIyBNZXJnZSBzbyB0aGF0IHdlIGluY2x1ZGUgb3VyIHNoYXBlIG1ldGFkYXRhLgplbGVjdGlvbl93aXRoX3NoYXBlIDwtIG1lcmdlKHN0YXRlU2hhcGUsIGVsZWN0aW9uX3dpdGhfbG9jYXRpb24sIGJ5LnggPSAic3RhdGUiLCBieS55ID0gInN0YXRlLngiKSAlPiUKICBzZWxlY3Qoc3RhdGUsIGNvdW50eV9maXBzLCBzdGF0ZV9wbywgY291bnR5X25hbWUsIHBhcnR5LCBjYW5kaWRhdGV2b3RlcywgbG9uLCBsYXQsIGdlb21ldHJ5KQoKIyBUcmFuc2Zvcm1hdGlvbiB0byBhIHZlcnkgc3BlY2lmaWMgZGF0YXR5cGUgdGhhdCBsZWFmbGV0IHdhbnRzIHRvIHVzZSB3aGVuIG1hcHBpbmcgY2hvcm9wbGV0aCBtYXBzLiAKZWxlY3Rpb25fZmluYWwgPC0gc3RfYXNfc2YoZWxlY3Rpb25fd2l0aF9zaGFwZSwgY29vcmRzID0gYygibG9uIiwgImxhdCIpLCBjcnMgPSA0MzI2KQoKIyBTaG93IG91ciBmaW5hbCBkYXRhc2V0LAprYWJsZShoZWFkKGVsZWN0aW9uX2ZpbmFsLCAyKSwgcm93Lm5hbWVzID0gRkFMU0UpCmBgYAoKTmljZSEgV2UgaGF2ZSBhIHRyYW5zZm9ybWVkIGRhdGFzZXQgdGhhdCBjYW4gYmUgdXNlZCB3aXRoIHRoZSBwdXJwb3NlIG9mIHNob3dpbmcgd2hpY2ggc3RhdGVzIGFyZSBwcmltYXJseSBkZW1vY3JhdGljIG9yIHJlcHVibGljYW4uCgojIyBFbGVjdGlvbiBDaG9yb3BsZXRoIE1hcAoKVGhlIHB1cnBvc2Ugb2YgdGhlIHZpc3VhbGl6YXRpb24gYmVsb3cgaXMgdG8gc2hvdyB3aGljaCBzdGF0ZXMgYXJlIG1vcmUgYmlhc2VkIHRvd2FyZHMgYmVpbmcgcmVwdWJsaWNhbiBvciBkZW1vY3JhdC4gVGhlIHN0YXRlcyB0aGF0IGFyZSBhIGJyaWdodCByZWQgYXJlIGNvbnNpZGVyZWQgdG8gYmUgaGVhdmlseSBiaWFzZWQgdG93YXJkcyBiZWluZyByZXB1YmxpY2FuLiBUaGUgc3RhdGVzIHRoYXQgYXJlIGEgYnJpZ2h0IGJsdWUgYXJlIGNvbnNpZGVyZWQgdG8gYmUgaGVhdmlseSBiaWFzZWQgdG93YXJkcyBiZWluZyBkZW1vY3JhdGljLiBUaGUgc3RhdGVzIHdoaWNoIGFyZSBhIHB1cnBsZS1pc2ggY29sb3IgcmVwcmVzZW50IGEgZGl2aWRlIG9mIHJlcHVibGljYW5zIGFuZCBkZW1vY3JhdHMuIFdlIGxldmVyYWdlIHRoZSB0b3RhbCBhbW91bnQgb2YgY2FuZGlkYXRlIHZvdGVzIGluIGEgZ2l2ZW4gc3RhdGUsIGdyb3VwZWQgYnkgdGhlaXIgcGFydHkgdG8gZGV0ZXJtaW5lIHRoZSBjb2xvciBpbnRlbnNpdHkuICAgCgpgYGB7cn0KCmdldF9jb2xvciA8LSBmdW5jdGlvbihwYXJ0eSkgewogIGlmIChwYXJ0eSA9PSAiUkVQVUJMSUNBTiIpIHsKICAgIHJldHVybigicmVkIikKICB9IGVsc2UgewogICAgcmV0dXJuKCJibHVlIikKICB9Cn0KCiMgUGxvdCB0aGUgZWxlY3Rpb24gcmVzdWx0cyB3aXRoIGN1c3RvbWl6ZWQgcG9seWdvbiBmZWF0dXJlcy4KZWxlY3Rpb25fbWFwIDwtIGxlYWZsZXQoKSAlPiUKICBzZXRWaWV3KGxuZyA9IC05OC41ODMzLCBsYXQgPSAzOS44MzMzLCB6b29tID0gNCkgJT4lICAgIyBDZW50ZXJlZCBvbiB0aGUgVVMgCiAgYWRkUG9seWdvbnMoZGF0YSA9IGVsZWN0aW9uX2ZpbmFsLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IGlmZWxzZShlbGVjdGlvbl9maW5hbCRwYXJ0eSA9PSAiUkVQVUJMSUNBTiIsICJyZWQiLCAiYmx1ZSIpLAogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC41LAogICAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgICAgICB3ZWlnaHQgPSAxLAogICAgICAgICAgICAgIGxhYmVsID0gfnBhc3RlKCdTdGF0ZTogJywgc3RhdGUpKQoKIyBBZGQgYSBzaW1wbGUgbGVnZW5kLgplbGVjdGlvbl9tYXAgPC0gYWRkTGVnZW5kKAogIGVsZWN0aW9uX21hcCwKICBwb3NpdGlvbiA9ICJ0b3ByaWdodCIsCiAgY29sb3JzID0gYygiYmx1ZSIsICJyZWQiKSwKICBsYWJlbHMgPSBjKCJEZW1vY3JhdCIsICJSZXB1YmxpY2FuIiksCiAgdGl0bGUgPSAiRWxlY3Rpb24gT3V0Y29tZSIKKQoKCmVsZWN0aW9uX21hcApgYGA=